CloudFrontへの”cloudfront.net”ドメインでのアクセスをCloudFront Functionsでブロックしてみた
はじめに
清水です。AWSのCDNサービスであるAmazon CloudFrontでは自動的に割り当てられるドメイン、もしくは設定した独自のドメインでDistributionにアクセスすることができます。独自ドメイン(例えばwww.example.com
)を使用してユーザにアクセスさせる場合には、この「自動的に割り当てられるドメイン」が不要になることもあります。
自動的に割り当てられるドメインはd111111abcdef8.cloudfront.net
といった形式のcloudfront.net
のサブドメインに該当するもので、プレフィックスとなる先頭のd
以外はランダムな英数字です。当てずっぽうでアクセスされることはあまりないかと思いますが、完全にないとは言い切れません。また独自ドメインをCNAMEレコードで登録している場合は、名前解決の過程でこのcloudfront.net
のサブドメインが判明してしまいます。cloudfront.net
のサブドメインによる意図しないアクセスが発生しうるため、ユーザのCloudFront Distributionへのアクセスは設定した独自ドメインのみ許可して、自動的に割り当てられるデフォルトのドメインからのアクセスはブロックしたい、というケースもあるかと思います。
この自動的に割り当てられるcloudfront.net
のサブドメインでのアクセスをブロックする方法としては、いくつかの方法が考えられるかと思います。今回はCloudFront Functionsを用いて実現してみました。
CloudFront Functionsで"cloudfront.net"ドメインでのアクセスをブロックしてみた
CloudFront Distributionの準備
まずは動作検証に使用するCloudFront Distributionを作成していきます。オリジンにはApache HTTP Serverの稼働するEC2インスタンスを準備しました。ルートオブジェクトindex.html
にアクセスすると以下のようにシンプルなテキストを返します。
% curl http://ec2-18-XXX-XXX-172.ap-northeast-1.compute.amazonaws.com <html> <head><title>block-default-domain-access</title></head> v <body>block-default-domain-access.example.net</body> </html>
このEC2のPublic IPv4 DNSをOrigin domainとしてDistributionを作成します。この段階ではまだCloudFront Functionsは関連付けません。(のちほど設定します。)
独自ドメインまわりは以下のように設定しました。Alternate domain name (CNAME)にユーザがアクセスする想定のドメインとなるblock-default-domain-access.example.net
を設定します。
Distributionが作成できました。
あわせて、block-default-domain-access.example.net
のDNS側のレコード設定も実施しておきます。今回はRoute 53上でA ALIASレコードを登録しました。
"cloudfront.net"ドメインをブロックしていないときの状況を確認
さて、CloudFrontのデフォルトドメインd111111abcdef8.cloudfront.net
でのアクセスをブロックしていない状況での動作を確認しておきましょう。
独自に設定したドメインでのアクセスはもちろん可能です。今回、CloudFront DistributionのBehavior設定でHTTP、HTTPSの双方のアクセスに応ずるよう設定してあるので、どちらでもアクセスすることができます。
% curl https://block-default-domain-access.example.net <html> <head><title>block-default-domain-access</title></head> <body>block-default-domain-access.example.net</body> </html>
% curl http://block-default-domain-access.example.net <html> <head><title>block-default-domain-access</title></head> <body>block-default-domain-access.example.net</body> </html>
続いてCloudFrontのデフォルトドメインd111111abcdef8.cloudfront.net
でのアクセスについてです。こちらもHTTPS、HTTPの双方でアクセスすることができます。
(余談ですが、筆者はこの動作確認をするまで、てっきりHTTPSのアクセスは証明書関連でエラーが出現するかと思っていました。独自ドメイン用の証明書を設定してもcloudfront.net用の証明書も有効、どちらのHTTPSアクセスも可能なんですね。ただし、CloudFrontとオリジンとの通信がHTTPSの場合で、オリジンの証明書の状況などによっては、CloudFrontからオリジンへの接続不可といったエラーが出る可能性があります。)
% curl https://d34loxxxxxxxxx.cloudfront.net <html> <head><title>block-default-domain-access</title></head> <body>block-default-domain-access.example.net</body> </html>
% curl http://d34loxxxxxxxxx.cloudfront.net <html> <head><title>block-default-domain-access</title></head> <body>block-default-domain-access.example.net</body> </html>
"cloudfront.net"ドメインでのアクセスをブロックするCloudFront Functionsの設定
続いて本題となる、CloudFront Distributionのデフォルトのcloudfront.net
ドメインでアクセスしたらブロックする処理をCloudFront Functionsで設定します。
CloudFrontマネジメントコンソールのFunctionsから[Create function]ボタンで進み、NameとDescriptionを入力してFunctionを作成します。
続く画面、BuildタブのFunction codeのDevelopmentを書き換えます。
CloudFront Functionsのコードは以下となります。(なお筆者はコーディングに明るくなく、見様見真似で作成したコードとなります。必ずしも適切なコードではなく、あくまで動作検証を最優先としている点にご注意ください。)Hostヘッダにcloudfront.net
が含まれていたら403 Forbidden
を返す処理としています。
function handler(event) { var request = event.request; var host = request.headers.host.value; if (host.includes('cloudfront.net')) { return { statusCode: 403, statusDescription: 'Forbidden', body: { "encoding": "text", "data": "<html><head><title>403 Forbidden</title></head><body><center><h1>403 Forbidden</h1></center></body></html>" } }; } return request; }
[Save changes]ボタンでコードを保存します。続いてTestタブに移りTest functionでコードをテストします。Event typeはViewer request
を選択、Request headersでまずは独自ドメインblock-default-domain-access.example.net
でのアクセス想定のテストです。403 Forbidden
にならずにリクエストをそのまま返していますね。
続いて今回のポイントとなるcloudfront.net
ドメインでアクセスした想定のテストです。こちらは想定通り403 Forbidden
が返りました。
なお、今回のコードではcloudfront.net.example.net
というような「cloudfront.net
が含まれる独自ドメイン」はカバーできていません。CloudFrontのデフォルトドメインではありませんが403 Forbidden
を返す挙動となります。厳密には意図と異なる動作となりますが、今回は目をつぶっておきます。
またHostヘッダが存在しない場合、Failed
となりCloudFront Functionsの実行に失敗します。基本的にCloudFrontへのリクエストはHostヘッダが必須である認識ですので(CloudFrontで独自ドメイン利用の際にディストリビューション側にもCNAME設定が必要な点をその動作とともに検証してみた | DevelopersIO)、今回はこちらのケースも黙認します。(あくまで動作検証が目的ということで。)
ということで、このコードで動作確認OKとして、[Publish function]します。
Associated distributionsの項目が現れるので、[Add association]ボタンでFunctionをDistributionに関連付けます。
該当Distribution IDを選択、Event typeはViewer request
を選択します。今回はDefault (*) のCache behaviorに関連付けました。
Functionの関連付けが完了しました。
"cloudfront.net"ドメインでのアクセスをブロックするCloudFront Functionsの動作確認
それではcloudfront.net
ドメインをブロックした環境にアクセスしてみましょう。自動で割り当てられるcloudfornt.net
のサブドメインでアクセスすると、HTTPS/HTTPとも以下のように403 Forbidden
が返りました。
% curl -i https://d34loxxxxxxxxx.cloudfront.net HTTP/2 403 server: CloudFront date: Sat, 15 Jul 2023 11:33:44 GMT content-length: 106 x-cache: FunctionGeneratedResponse from cloudfront via: 1.1 18fbxxxxxxxxxxxxxxxxxxxxxxxx6f10.cloudfront.net (CloudFront) x-amz-cf-pop: NRT57-P3 x-amz-cf-id: 3pe9xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxWw== <html><head><title>403 Forbidden</title></head><body><center><h1>403 Forbidden</h1></center></body></html>
% curl -i http://d34loxxxxxxxxx.cloudfront.net HTTP/1.1 403 Forbidden Server: CloudFront Date: Sat, 15 Jul 2023 11:34:09 GMT Content-Length: 106 Connection: keep-alive X-Cache: FunctionGeneratedResponse from cloudfront Via: 1.1 026dxxxxxxxxxxxxxxxxxxxxxxxx80c6.cloudfront.net (CloudFront) X-Amz-Cf-Pop: NRT57-P3 X-Amz-Cf-Id: 5gCzxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx4w== <html><head><title>403 Forbidden</title></head><body><center><h1>403 Forbidden</h1></center></body></html>
続いて独自ドメインでのアクセスです。こちらは以下のように、通常のアクセスができますね。
% curl -i https://block-default-domain-access.example.net HTTP/2 200 content-type: text/html; charset=UTF-8 content-length: 128 date: Sat, 15 Jul 2023 08:00:41 GMT server: Apache/2.4.27 (Amazon) PHP/5.6.35 last-modified: Sat, 15 Jul 2023 07:59:25 GMT etag: "80-60xxxxxxxxbb3" accept-ranges: bytes x-cache: Hit from cloudfront via: 1.1 4004xxxxxxxxxxxxxxxxxxxxxxxx96f4.cloudfront.net (CloudFront) x-amz-cf-pop: NRT57-C4 x-amz-cf-id: KIOexxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxIA== age: 12824 <html> <head><title>block-default-domain-access</title></head> <body>block-default-domain-access.example.net</body> </html>
% curl -i http://block-default-domain-access.example.net HTTP/1.1 200 OK Content-Type: text/html; charset=UTF-8 Content-Length: 128 Connection: keep-alive Date: Sat, 15 Jul 2023 08:00:41 GMT Server: Apache/2.4.27 (Amazon) PHP/5.6.35 Last-Modified: Sat, 15 Jul 2023 07:59:25 GMT ETag: "80-60xxxxxxxxbb3" Accept-Ranges: bytes X-Cache: Hit from cloudfront Via: 1.1 8ea6xxxxxxxxxxxxxxxxxxxxxxxx1612.cloudfront.net (CloudFront) X-Amz-Cf-Pop: NRT57-C4 X-Amz-Cf-Id: P4w2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-Q== Age: 12839 <html> <head><title>block-default-domain-access</title></head> <body>block-default-domain-access.example.net</body> </html>
"cloudfront.net"ドメインでアクセスした場合に独自ドメインにリダイレクトさせるパターン
CloudFront Functionsのコードを以下のように変更すれば、CloudFrontのデフォルトドメインにアクセスした場合に本来の独自ドメインにリダイレクトさせる、ということも可能です。(なお以下のコードについては、もともとの(デフォルトドメインへの)リダイレクトに対するドメイン以外、例えばリクエストのパスやクエリ文字列などはリダイレクト先独自ドメインに引き継ぐようにはなっていないのでご注意ください。)
function handler(event) { var request = event.request; var host = request.headers.host.value; var newurl = 'https://block-default-domain-access.example.net/'; if (host.includes('cloudfront.net')) { return { statusCode: 301, statusDescription: 'Moved Permanently', headers: { "location": { "value": newurl } } }; } return request; }
デフォルトのcloudfornt.net
のサブドメインでアクセスしてみます。301
でリダイレクトされていますね。
% curl -i https://d34loxxxxxxxxx.cloudfront.net HTTP/2 301 server: CloudFront date: Sat, 15 Jul 2023 13:51:08 GMT content-length: 0 location: https://block-default-domain-access.example.net/ x-cache: FunctionGeneratedResponse from cloudfront via: 1.1 3a5axxxxxxxxxxxxxxxxxxxxxxxx3916.cloudfront.net (CloudFront) x-amz-cf-pop: NRT57-P3 x-amz-cf-id: unwnxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxNg==
独自ドメインをCNAMEレコードで登録してた場合のデフォルトドメインの判明について
CloudFront Functionsを用いたcloudfront.net
サブドメインへのアクセスのブロックについて確認してきました。ここで、冒頭で述べた「独自ドメインをCNAMEレコードで登録している場合は、名前解決の過程でcloudfront.net
のサブドメインが判明してしまう」、という事象について改めて確認してみます。
まず今回の動作検証のように、独自ドメインをA ALIASレコードで登録した場合です。CloudFront + Route 53の構成ではこちらが推奨されます。メリットしかありません。(Amazon Route 53のALIASレコード利用のススメ | DevelopersIO)
dig
コマンドで独自ドメインの名前解決をしてみると、直接IPアドレスが返ってきます。A ALIASレコードを利用する限りはcloudfront.net
サブドメインが判明することはなさそうです。
% dig block-default-domain-access.example.net ; <<>> DiG 9.10.6 <<>> block-default-domain-access.example.net ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 52665 ;; flags: qr rd ra; QUERY: 1, ANSWER: 4, AUTHORITY: 0, ADDITIONAL: 1 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 1220 ;; QUESTION SECTION: ;block-default-domain-access.example.net. IN A ;; ANSWER SECTION: block-default-domain-access.example.net. 60 IN A 18.65.206.15 block-default-domain-access.example.net. 60 IN A 18.65.206.33 block-default-domain-access.example.net. 60 IN A 18.65.206.101 block-default-domain-access.example.net. 60 IN A 18.65.206.93 ;; Query time: 36 msec ;; SERVER: 192.168.11.1#53(192.168.11.1) ;; WHEN: Sat Jul 15 22:53:15 JST 2023 ;; MSG SIZE rcvd: 132
対して、独自ドメインをCNAMEレコードで登録してみた場合です。CloudFront + Route 53の構成でも以下のようにCNAMEレコードでの登録自体は可能です。
独自ドメインのCNAMEレコードをdig
コマンドで名前解決した結果が下記になります。最終的にIPアドレスが返ってくるのですが、いちどCloudFrontのデフォルトのドメイン名に名前解決してから、そのcloudfront.net
サブドメインを名前解決するかたちでIPアドレスを得ています。独自ドメインをCNAMEレコードで登録している場合は名前解決の際にcloudfront.net
のサブドメインが判明してしまうことが確認できましたね。
% dig block-default-domain-access.example.net ; <<>> DiG 9.10.6 <<>> block-default-domain-access.example.net ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 5093 ;; flags: qr rd ra; QUERY: 1, ANSWER: 5, AUTHORITY: 0, ADDITIONAL: 1 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 1220 ;; QUESTION SECTION: ;block-default-domain-access.example.net. IN A ;; ANSWER SECTION: block-default-domain-access.example.net. 60 IN CNAME d34loxxxxxxxxx.cloudfront.net. d34loxxxxxxxxx.cloudfront.net. 60 IN A 18.65.206.101 d34loxxxxxxxxx.cloudfront.net. 60 IN A 18.65.206.15 d34loxxxxxxxxx.cloudfront.net. 60 IN A 18.65.206.33 d34loxxxxxxxxx.cloudfront.net. 60 IN A 18.65.206.93 ;; Query time: 35 msec ;; SERVER: 192.168.11.1#53(192.168.11.1) ;; WHEN: Sun Jul 16 01:37:57 JST 2023 ;; MSG SIZE rcvd: 172
ほかの実現方法との簡単な比較
さて、冒頭で「cloudfront.net
サブドメインでのアクセスをブロックする方法は複数ある」ということを述べました。ほかに考えられる実現方法とその簡単な比較を行ってみましょう。
1つ目はAWS WAFを用いた実現方法が検討できるかと思います。今回のCloudFront Functionsのコードで実現したような、「hostヘッダにcloudfront.net
という文字列が含まれていたらブロックする」というRuleを作成、このRuleを使用するWeb ACLをCloudFront Distributionに関連付けるかたちです。AWS WAFでのHostヘッダの扱いについては以下ブログエントリなどをご確認ください。
AWS WAFを用いた方法と今回のCloudFront Functionsで実現する方法、まずはコスト面を比較してみます。AWS WAFの場合は月々の固定費として1つのWeb ACLあたり$5、1つのRuleあたり$1の費用がかかります。さらにリクエスト料金として100万リクエストあたり$0.6が発生します。対して、CloudFront Functionsは固定費はなく、100万リクエストあたり$0.1の費用のみ発生です。固定費が発生せず、リクエスト単価も安いことからコスト面だけみればCloudFront Functionsがよさそうです。
- AWS WAF
- Web ACL $5.00/month
- Rule $1.00/month
- リクエスト料金 $0.60/100万リクエスト
- CloudFront Functions
- リクエスト料金 $0.10/100万リクエスト
ただし、AWS WAFでほかのRuleも適用する(適用している)といった場合は、わざわざCloudFront Functionsを使わずとも該当WAFにRuleを追加する、というかたちのほうがコストを抑えながら実現できるかもしれません。またCloudFront Functionsですでに特定の処理を実行しており、今回のようなHostヘッダをチェックするコードを追加すると処理が煩雑になる、といった場合にはAWS WAF側で実現するというのも選択肢になりうるかと思います。コスト面を考慮しつつ、実際の環境にあわせた適切な実現方法を採るようにしましょう。
AWS WAFでの実現方法との比較をしてみましたが、そのほかにもオリジンサーバ側でHostヘッダをチェックする、という方法も採れますね。オリジン側でALBを使用していれば、ALBリスナールールでHostヘッダをチェックすることもできるかと考えます。(ALBへのアクセスを特定のHostヘッダのみに限定する方法の一例 | DevelopersIO)ただし、CloudFront側でHostヘッダをオリジンリクエストに含め、かつキャッシュキーとなるよう設定しておく必要がある点には注意しましょう。この点を考えると、CloudFront Distributionに関連付けたAWS WAFやCloudFront FunctionsのViewer requestなど、オリジンにリクエストが到達する前に処理するほうがシンプルになるかもしれません。
ということで、ざっと思いついたCloudFront Functions以外でのcloudfront.net
サブドメインからのアクセスをブロックする方法との比較でした。このほかにも実現方法はあるかもしれません。
まとめ
独自ドメインを設定したCloudFront Distributionに対して、デフォルトのd111111abcdef8.cloudfront.net
形式のドメインでアクセスできないようにCloudFront Functionsを用いて設定してみました。自動的に割り当てられるcloudfront.net
のサブドメインでアクセスした場合には独自ドメインにリダイレクトさせる、といったことも実現可能です。デフォルトのドメイン名でCloudFront Distributionにアクセスさせたくない場合は設定を検討してみましょう。またCloudFront Functions以外の方法でも実現可能なので、コストや構成のシンプルさなどを考慮し最適な方法を選択しましょう。